#ifndef _Color_H_
#define _Color_H_

#pragma once
#include "LightingRender.h"

class Color {
public:
	Float r, g, b;
	Color() :r(0), g(0), b(0) {}
	Color(Float g) : r(g), g(g), b(g) {}
	Color(Float r, Float g, Float b) : r(r), g(g), b(b) {}
	~Color() {}

	inline void operator=(const Color &c) {
		r = c.r, g = c.g, b = c.b;
	}

	inline Color operator+(const Color &c) const {
		return Color(r + c.r, g + c.g, b + c.b);
	}

	inline Color operator-(const Color &c) const {
		return Color(r - c.r, g - c.g, b - c.b);
	}

	template <class T>
	inline Color operator*(T s) const {
		return Color(r * Float(s), g * Float(s), b * Float(s));
	}

	template <class T>
	inline Color operator/(T s) const {
		Float t = 1.0f / Float(s);
		return Color(r * t, g * t, b * t);
	}

	inline Color operator*(const Color &c) const {
		return Color(r * c.r, g * c.g, b * c.b);
	}

	inline Color operator/(const Color &c) const {
		return Color(r / c.r, g / c.g, b / c.b);
	}

	template <class T>
	inline Color & operator*=(T s) {
		r *= Float(s), g *= Float(s), b *= Float(s);
		return *this;
	}

	template <class T>
	inline Color & operator/=(T s) {
		Float t = 1.0f / Float(s);
		r *= t, g *= t, b *= t;
		return *this;
	}

	inline Color & operator+=(const Color & c) {
		r += c.r, g += c.g, b += c.b;
		return *this;
	}

	inline Color & operator-=(const Color & c) {
		r -= c.r, g -= c.g, b -= c.b;
		return *this;
	}

	inline Color & operator*=(const Color & c) {
		r *= c.r, g *= c.g, b *= c.b;
		return *this;
	}

	inline Color & operator/=(const Color & c) {
		r /= c.r, g /= c.g, b /= c.b;
		return *this;
	}

	inline bool operator==(const Color &c) const {
		return r == c.r && g == c.g && b == c.b;
	}

	inline bool operator!=(const Color &c) const {
		return r != c.r || g != c.g || b != c.b;
	}

	inline bool isBlack() const {
		return r <= DOUBLE_EPS && g <= DOUBLE_EPS && b <= DOUBLE_EPS;
	}

	inline bool isZero() const {
		return ISZERO(r) && ISZERO(g) && ISZERO(b);
	}

	template <class T>
	inline friend Color operator*(T k, const Color &c) {
		return Color(Float(k) * c.r, Float(k) * c.g, Float(k) * c.b);
	}

	template <class T>
	inline friend Color & operator*=(T k, Color &c) {
		c.r *= Float(k), c.g *= Float(k), c.b *= Float(k);
		return c;
	}

	inline void clear() {
		r = 0, g = 0, b = 0;
	}

	inline Color & clamp() {
		r = CLAMP(r);
		g = CLAMP(g);
		b = CLAMP(b);
		return *this;
	}

	inline Color & clampInRange(Float min, Float max) {
		r = CLAMPX(r, min, max);
		g = CLAMPX(g, min, max);
		b = CLAMPX(b, min, max);
		return *this;
	}

	inline Color & clampNegative() {
		r = r < 0 ? 0 : r;
		g = g < 0 ? 0 : g;
		b = b < 0 ? 0 : b;
		return *this;
	}

	inline Color & opposite() {
		r = 1 - r;
		g = 1 - g;
		b = 1 - b;
		return *this;
	}

	inline Color & exp() {
		r = std::exp(r);
		g = std::exp(g);
		b = std::exp(b);
		return *this;
	}

	inline Color & gammaCorrect(Float gamma) {
		Float oneOverGamma = 1 / gamma;
		r = pow(r, oneOverGamma);
		g = pow(g, oneOverGamma);
		b = pow(b, oneOverGamma);
		return *this;
	}

	inline Color & gammaCorrect_inv(Float oneOverGamma) {
		r = pow(r, oneOverGamma);
		g = pow(g, oneOverGamma);
		b = pow(b, oneOverGamma);
		return *this;
	}

	inline Color gammaCorrect(Float gamma) const {
		Float oneOverGamma = 1 / gamma;
		return Color(pow(r, oneOverGamma), pow(g, oneOverGamma), pow(b, oneOverGamma));
	}

	inline Color gammaCorrect_inv(Float oneOverGamma) const {
		return Color(pow(r, oneOverGamma), pow(g, oneOverGamma), pow(b, oneOverGamma));
	}

	inline friend Color darken(const Color &a, const Color &b) {
		return Color(MIN(a.r, b.r), MIN(a.g, b.g), MIN(a.b, b.b));
	}

	inline friend Color lighten(const Color &a, const Color &b) {
		return Color(MAX(a.r, b.r), MAX(a.g, b.g), MAX(a.b, b.b));
	}

	inline friend Color difference(const Color &a, const Color &b) {
		Color c = a - b;
		c.r = abs(c.r);
		c.g = abs(c.g);
		c.b = abs(c.b);
		return c;
	}

	inline Color & setRGBInt(int rgb) {
		r = ((rgb >> 16) & 0xFF) / 255.0f;
		g = ((rgb >> 8) & 0xFF) / 255.0f;
		b = (rgb & 0xFF) / 255.0f;
		return *this;
	}

	inline Float getLuminance() const {
		return (0.2989f * r) + (0.5866f * g) + (0.1145f * b);
	}

	inline Float getMax() const {
		return MAX(r, MAX(g, b));
	}

	inline Float getMin() const {
		return MIN(r, MIN(g, b));
	}

	inline Float getAverage() const {
		return (r + g + b) / 3;
	}

	inline int toRGBInt() const {
		int ir = (int)(r * 255 + 0.5);
		int ig = (int)(g * 255 + 0.5);
		int ib = (int)(b * 255 + 0.5);
		ir = CLAMPX(ir, 0, 255);
		ig = CLAMPX(ig, 0, 255);
		ib = CLAMPX(ib, 0, 255);
		return (ir << 16) | (ig << 8) | ib;
	}

	inline void toByte(Byte & rb, Byte & gb, Byte & bb) const {
		rb = Byte(CLAMPX(r * 255 + 0.5, 0, 255));
		gb = Byte(CLAMPX(g * 255 + 0.5, 0, 255));
		bb = Byte(CLAMPX(b * 255 + 0.5, 0, 255));
	}

	inline string str() const {
		ostringstream ss;
		ss << "Color(" << r << "," << g << "," << b << ")";
		return ss.str();
	}
};

inline Color ColorByte(Byte r, Byte g, Byte b) {
	const Float s = 1.0f / 255;
	return Color(r, g, b) *= s;
}

Color HSV2Color(Float h, Float s, Float v);

namespace Colors {
	const Color Black = Color(0.0, 0.0, 0.0);
	const Color White = Color(1.0, 1.0, 1.0);
	const Color Red = Color(1.0, 0.0, 0.0);
	const Color Green = Color(0.0, 1.0, 0.0);
	const Color Blue = Color(0.0, 0.0, 1.0);
}

#endif // _Color_H_